package com.cm.sys.framework.utils;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.cm.sys.framework.application.ApiResponseCode;
import com.cm.sys.framework.execption.BusinessException;
import com.cm.sys.system.entity.User;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;

/***
 * jwt的工具类
 * 
 * @author yxq18
 *
 */
public class JwtUtils {

    private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);
    public static final long EXPIRATION_TIME = 43200000L; // 12 hour
    static final String SECRET = "ThisIsASecret";

    /**
     * 用户登录成功后生成Jwt 使用Hs256算法 私匙使用用户密码
     *
     * @param ttlMillis
     *            jwt过期时间
     * @param user
     *            登录成功的user对象
     * @return
     */
    public static String createJWT(User user) {
        // 指定签名的时候使用的签名算法，也就是header那部分，jjwt已经将这部分内容封装好了。
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        // 生成JWT的时间
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        // 创建payload的私有声明（根据特定的业务需要添加，如果要拿这个做验证，一般是需要和jwt的接收方提前沟通好验证方式的）
        Map<String, Object> claims = new HashMap<String, Object>();
        claims.put("id", user.getId());
        claims.put("userName", user.getUserName());
        claims.put("password", user.getPassword());
        claims.put("userRole", user.getUserRole());

        // 生成签名的时候使用的秘钥secret,这个方法本地封装了的，一般可以从本地配置文件中读取，切记这个秘钥不能外露哦。它就是你服务端的私钥，在任何场景都不应该流露出去。一旦客户端得知这个secret,
        // 那就意味着客户端是可以自我签发jwt了。
        String key = SECRET;

        // 生成签发人
        String subject = user.getUserName();

        // 下面就是在为payload添加各种标准声明和私有声明了
        // 这里其实就是new一个JwtBuilder，设置jwt的body
        JwtBuilder builder = Jwts.builder()
            // 如果有私有声明，一定要先设置这个自己创建的私有的声明，这个是给builder的claim赋值，一旦写在标准的声明赋值之后，就是覆盖了那些标准的声明的
            .setClaims(claims)
            // 设置jti(JWT ID)：是JWT的唯一标识，根据业务需要，这个可以设置为一个不重复的值，主要用来作为一次性token,从而回避重放攻击。
            .setId(UUID.randomUUID().toString())
            // iat: jwt的签发时间
            .setIssuedAt(now)
            // 代表这个JWT的主体，即它的所有人，这个是一个json格式的字符串，可以存放什么userid，roldid之类的，作为什么用户的唯一标志。
            .setSubject(subject)
            // 设置签名使用的签名算法和签名使用的秘钥
            .signWith(signatureAlgorithm, key);
        if (EXPIRATION_TIME >= 0) {
            long expMillis = nowMillis + EXPIRATION_TIME;
            Date exp = new Date(expMillis);
            // 设置过期时间
            logger.info("=jwt令牌的过期时间=【{}】=========", exp);
            builder.setExpiration(exp);
        }
        return builder.compact();
    }

    /**
     * Token的解密
     * 
     * @param token
     *            加密后的token
     * @param user
     *            用户的对象
     * @return
     */
    public static Claims parseJWT(String token) {
        // 签名秘钥，和生成的签名的秘钥一模一样
        // 得到DefaultJwtParser
        Claims claims = Jwts.parser()
            // 设置签名的秘钥
            .setSigningKey(SECRET)
            // 设置需要解析的jwt
            .parseClaimsJws(token).getBody();
        return claims;
    }

    public static User validateToken(String token) {

        User user = new User();
        if (token != null) {
            // 解析token
            try {
                Map<String, Object> body = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
                String userName = (String)(body.get("userName"));
                String password = (String)body.get("password");
                Integer id = (Integer)(body.get("id"));
                Integer userRole = (Integer) body.get("userRole");
                user.setId(id);
                user.setPassword(password);
                user.setUserName(userName);
                user.setUserRole(userRole);
                if (userName == null || userName.isEmpty()) {
                    throw new BusinessException(ApiResponseCode.TOKEN_ERROR.getCode(),
                        ApiResponseCode.TOKEN_ERROR.getMessage());
                }

            } catch (SignatureException | MalformedJwtException e) {
                throw new BusinessException(ApiResponseCode.TOKEN_ERROR.getCode(),
                    ApiResponseCode.TOKEN_ERROR.getMessage());
            } catch (ExpiredJwtException e) {
                // jwt 已经过期，在设置jwt的时候如果设置了过期时间，这里会自动判断jwt是否已经过期，如果过期则会抛出这个异常，我们可以抓住这个异常并作相关处理。
                throw new BusinessException(ApiResponseCode.TOKEN_EXP.getCode(),
                    ApiResponseCode.TOKEN_EXP.getMessage());
            }
        } else {
            throw new BusinessException(ApiResponseCode.NO_TOKEN.getCode(), ApiResponseCode.NO_TOKEN.getMessage());
        }
        return user;
    }

    /**
     * 校验token 在这里可以使用官方的校验，我这里校验的是token中携带的密码于数据库一致的话就校验通过
     * 
     * @param token
     * @param user
     * @return
     */
    public static Boolean isVerify(String token, User user) {

        // 得到DefaultJwtParser
        Claims claims = Jwts.parser()
            // 设置签名的秘钥
            .setSigningKey(SECRET)
            // 设置需要解析的jwt
            .parseClaimsJws(token).getBody();

        if (claims.get("password").equals(user.getPassword())) {
            return true;
        }

        return false;
    }
}
